/**
 * CoolClock 2.1.4
 * Copyright 2010, Simon Baird
 * Released under the BSD License.
 *
 * Display an analog clock using canvas.
 * http://randomibis.com/coolclock/
 *
 */

// Constructor for CoolClock objects
window.CoolClock = function(options) {
  return this.init(options);
}

// Config contains some defaults, and clock skins
CoolClock.config = {
  tickDelay: 1000,
  longTickDelay: 15000,
  defaultRadius: 85,
  renderRadius: 100,
  defaultSkin: "swissRail",
  // Should be in skin probably...
  // (TODO: allow skinning of digital display)
  showSecs: true,
  showAmPm: true,

  skins:  {
    // There are more skins in moreskins.js
    // Try making your own skin by copy/pasting one of these and tweaking it
    swissRail: {
      outerBorder: { lineWidth: 2, radius:95, color: "black", alpha: 1 },
      smallIndicator: { lineWidth: 2, startAt: 88, endAt: 92, color: "black", alpha: 1 },
      largeIndicator: { lineWidth: 4, startAt: 79, endAt: 92, color: "black", alpha: 1 },
      hourHand: { lineWidth: 8, startAt: -15, endAt: 50, color: "black", alpha: 1 },
      minuteHand: { lineWidth: 7, startAt: -15, endAt: 75, color: "black", alpha: 1 },
      secondHand: { lineWidth: 1, startAt: -20, endAt: 85, color: "red", alpha: 1 },
      secondDecoration: { lineWidth: 1, startAt: 70, radius: 4, fillColor: "red", color: "red", alpha: 1 }
    },
    chunkySwiss: {
      outerBorder: { lineWidth: 4, radius:97, color: "black", alpha: 1 },
      smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "black", alpha: 1 },
      largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "black", alpha: 1 },
      hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "black", alpha: 1 },
      minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "black", alpha: 1 },
      secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
      secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
    },
    chunkySwissOnBlack: {
      outerBorder: { lineWidth: 4, radius:97, color: "white", alpha: 1 },
      smallIndicator: { lineWidth: 4, startAt: 89, endAt: 93, color: "white", alpha: 1 },
      largeIndicator: { lineWidth: 8, startAt: 80, endAt: 93, color: "white", alpha: 1 },
      hourHand: { lineWidth: 12, startAt: -15, endAt: 60, color: "white", alpha: 1 },
      minuteHand: { lineWidth: 10, startAt: -15, endAt: 85, color: "white", alpha: 1 },
      secondHand: { lineWidth: 4, startAt: -20, endAt: 85, color: "red", alpha: 1 },
      secondDecoration: { lineWidth: 2, startAt: 70, radius: 8, fillColor: "red", color: "red", alpha: 1 }
    }

  },

  // Test for IE so we can nurse excanvas in a couple of places
  isIE: !!document.all,

  // Will store (a reference to) each clock here, indexed by the id of the canvas element
  clockTracker: {},

  // For giving a unique id to coolclock canvases with no id
  noIdCount: 0
};

// Define the CoolClock object's methods
CoolClock.prototype = {

  // Initialise using the parameters parsed from the colon delimited class
  init: function(options) {
    // Parse and store the options
    this.canvasId       = options.canvasId;
    this.skinId         = options.skinId || CoolClock.config.defaultSkin;
    this.displayRadius  = options.displayRadius || CoolClock.config.defaultRadius;
    this.showSecondHand = typeof options.showSecondHand == "boolean" ? options.showSecondHand : true;
    this.gmtOffset      = (options.gmtOffset != null && options.gmtOffset != '') ? parseFloat(options.gmtOffset) : null;
    this.showDigital    = typeof options.showDigital == "boolean" ? options.showDigital : false;
    this.logClock       = typeof options.logClock == "boolean" ? options.logClock : false;
    this.logClockRev    = typeof options.logClock == "boolean" ? options.logClockRev : false;

    this.tickDelay      = CoolClock.config[ this.showSecondHand ? "tickDelay" : "longTickDelay" ];

    // Get the canvas element
    this.canvas = document.getElementById(this.canvasId);

    // Make the canvas the requested size. It's always square.
    this.canvas.setAttribute("width",this.displayRadius*2);
    this.canvas.setAttribute("height",this.displayRadius*2);
    this.canvas.style.width = this.displayRadius*2 + "px";
    this.canvas.style.height = this.displayRadius*2 + "px";

    // Explain me please...?
    this.renderRadius = CoolClock.config.renderRadius;
    this.scale = this.displayRadius / this.renderRadius;

    // Initialise canvas context
    this.ctx = this.canvas.getContext("2d");
    this.ctx.scale(this.scale,this.scale);

    // Keep track of this object
    CoolClock.config.clockTracker[this.canvasId] = this;

    // Start the clock going
    //this.tick();

    return this;
  },

  // Draw a circle at point x,y with params as defined in skin
  fullCircleAt: function(x,y,skin) {
    this.ctx.save();
    this.ctx.globalAlpha = skin.alpha;
    this.ctx.lineWidth = skin.lineWidth;

    if (!CoolClock.config.isIE) {
      this.ctx.beginPath();
    }

    if (CoolClock.config.isIE) {
      // excanvas doesn't scale line width so we will do it here
      this.ctx.lineWidth = this.ctx.lineWidth * this.scale;
    }

    this.ctx.arc(x, y, skin.radius, 0, 2*Math.PI, false);

    if (CoolClock.config.isIE) {
      // excanvas doesn't close the circle so let's fill in the tiny gap
      this.ctx.arc(x, y, skin.radius, -0.1, 0.1, false);
    }

    if (skin.fillColor) {
      this.ctx.fillStyle = skin.fillColor
      this.ctx.fill();
    }
    else {
      // XXX why not stroke and fill
      this.ctx.strokeStyle = skin.color;
      this.ctx.stroke();
    }
    this.ctx.restore();
  },

  // Draw some text centered vertically and horizontally
  drawTextAt: function(theText,x,y) {
    this.ctx.save();
    this.ctx.font = '15px sans-serif';
    var tSize = this.ctx.measureText(theText);
    if (!tSize.height) tSize.height = 15; // no height in firefox.. :(
    this.ctx.fillText(theText,x - tSize.width/2,y - tSize.height/2);
    this.ctx.restore();
  },

  lpad2: function(num) {
    return (num < 10 ? '0' : '') + num;
  },

  tickAngle: function(second) {
    // Log algorithm by David Bradshaw
    var tweak = 3; // If it's lower the one second mark looks wrong (?)
    if (this.logClock) {
      return second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak));
    }
    else if (this.logClockRev) {
      // Flip the seconds then flip the angle (trickiness)
      second = (60 - second) % 60;
      return 1.0 - (second == 0 ? 0 : (Math.log(second*tweak) / Math.log(60*tweak)));
    }
    else {
      return second/60.0;
    }
  },

  timeText: function(hour,min,sec) {
    var c = CoolClock.config;
    return '' +
      (c.showAmPm ? ((hour%12)==0 ? 12 : (hour%12)) : hour) + ':' +
      this.lpad2(min) +
      (c.showSecs ? ':' + this.lpad2(sec) : '') +
      (c.showAmPm ? (hour < 12 ? ' am' : ' pm') : '')
    ;
  },

  // Draw a radial line by rotating then drawing a straight line
  // Ha ha, I think I've accidentally used Taus, (see http://tauday.com/)
  radialLineAtAngle: function(angleFraction,skin) {
    this.ctx.save();
    this.ctx.translate(this.renderRadius,this.renderRadius);
    this.ctx.rotate(Math.PI * (2.0 * angleFraction - 0.5));
    this.ctx.globalAlpha = skin.alpha;
    this.ctx.strokeStyle = skin.color;
    this.ctx.lineWidth = skin.lineWidth;

    if (CoolClock.config.isIE)
      // excanvas doesn't scale line width so we will do it here
      this.ctx.lineWidth = this.ctx.lineWidth * this.scale;

    if (skin.radius) {
      this.fullCircleAt(skin.startAt,0,skin)
    }
    else {
      this.ctx.beginPath();
      this.ctx.moveTo(skin.startAt,0)
      this.ctx.lineTo(skin.endAt,0);
      this.ctx.stroke();
    }
    this.ctx.restore();
  },

  render: function(hour,min,sec) {
    // Get the skin
    var skin = CoolClock.config.skins[this.skinId];
    if (!skin) skin = CoolClock.config.skins[CoolClock.config.defaultSkin];

    // Clear
    this.ctx.clearRect(0,0,this.renderRadius*2,this.renderRadius*2);

    // Draw the outer edge of the clock
    if (skin.outerBorder)
      this.fullCircleAt(this.renderRadius,this.renderRadius,skin.outerBorder);

    // Draw the tick marks. Every 5th one is a big one
    for (var i=0;i<60;i++) {
      (i%5)  && skin.smallIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.smallIndicator);
      !(i%5) && skin.largeIndicator && this.radialLineAtAngle(this.tickAngle(i),skin.largeIndicator);
    }

    // Write the time
    if (this.showDigital) {
      this.drawTextAt(
        this.timeText(hour,min,sec),
        this.renderRadius,
        this.renderRadius+this.renderRadius/2
      );
    }

    // Draw the hands
    if (skin.hourHand)
      this.radialLineAtAngle(this.tickAngle(((hour%12)*5 + min/12.0)),skin.hourHand);

    if (skin.minuteHand)
      this.radialLineAtAngle(this.tickAngle((min + sec/60.0)),skin.minuteHand);

    if (this.showSecondHand && skin.secondHand)
      this.radialLineAtAngle(this.tickAngle(sec),skin.secondHand);

    // Second hand decoration doesn't render right in IE so lets turn it off
    if (!CoolClock.config.isIE && this.showSecondHand && skin.secondDecoration)
      this.radialLineAtAngle(this.tickAngle(sec),skin.secondDecoration);
  },

  // Check the time and display the clock
  refreshDisplay: function() {
    var now = new Date();
    if (this.gmtOffset != null) {
      // Use GMT + gmtOffset
      var offsetNow = new Date(now.valueOf() + (this.gmtOffset * 1000 * 60 * 60));
      this.render(offsetNow.getUTCHours(),offsetNow.getUTCMinutes(),offsetNow.getUTCSeconds());
    }
    else {
      // Use local time
      this.render(now.getHours(),now.getMinutes(),now.getSeconds());
    }
  },

  // Set timeout to trigger a tick in the future
  nextTick: function() {
    setTimeout("CoolClock.config.clockTracker['"+this.canvasId+"'].tick()",this.tickDelay);
  },

  // Check the canvas element hasn't been removed
  stillHere: function() {
    return document.getElementById(this.canvasId) != null;
  },

  // Main tick handler. Refresh the clock then setup the next tick
  tick: function() {
    if (this.stillHere()) {
      this.refreshDisplay()
      this.nextTick();
    }
  }
};

// Find all canvas elements that have the CoolClock class and turns them into clocks
CoolClock.findAndCreateClocks = function() {
  // (Let's not use a jQuery selector here so it's easier to use frameworks other than jQuery)
  var canvases = document.getElementsByTagName("canvas");
  for (var i=0;i<canvases.length;i++) {
    // Pull out the fields from the class. Example "CoolClock:chunkySwissOnBlack:1000"
    var fields = canvases[i].className.split(" ")[0].split(":");
    if (fields[0] == "CoolClock") {
      if (!canvases[i].id) {
        // If there's no id on this canvas element then give it one
        canvases[i].id = '_coolclock_auto_id_' + CoolClock.config.noIdCount++;
      }
      // Create a clock object for this element
      return new CoolClock({
        canvasId:       canvases[i].id,
        skinId:         fields[1],
        displayRadius:  fields[2],
        showSecondHand: fields[3]!='noSeconds',
        gmtOffset:      fields[4],
        showDigital:    fields[5]=='showDigital',
        logClock:       fields[6]=='logClock',
        logClockRev:    fields[6]=='logClockRev'
      });
    }
  }
};

// If you don't have jQuery then you need a body onload like this: <body onload="CoolClock.findAndCreateClocks()">
// If you do have jQuery and it's loaded already then we can do it right now
if (window.jQuery) jQuery(document).ready(CoolClock.findAndCreateClocks);
